﻿using System;
using System.Collections.Generic;
using System.Linq;
using Roslyn.Compilers.Common;
using Roslyn.Compilers.CSharp;
using RoslynHelper;

namespace OperatorGenericConstraint
{
    internal class MethodOperatorsGenericConstraintRewriter : SyntaxRewriter
    {
        private ISemanticModel _semanticModel;
        private Dictionary<SyntaxKind, List<TypeConstraintSyntax>> _typeConstraints = new Dictionary<SyntaxKind, List<TypeConstraintSyntax>>();
        private List<OperatorGenericConstraintAnnotation> _operatorGenericConstraintAnnotation;

        public MethodOperatorsGenericConstraintRewriter(ISemanticModel semanticModel, List<OperatorGenericConstraintAnnotation> operatorGenericConstraintAnnotation)
        {
            _semanticModel = semanticModel;
            _operatorGenericConstraintAnnotation = operatorGenericConstraintAnnotation;
        }

        protected override SyntaxNode VisitMethodDeclaration(MethodDeclarationSyntax node)
        {
            var value = (MethodDeclarationSyntax)base.VisitMethodDeclaration(node);
            if (_typeConstraints.Any())
            {
                value =
                    value.SetGenericConstraints(value.ConstraintClauses).
                    AddParameters(_typeConstraints.Select(tc =>
                        {
                            string op;
                            switch (tc.Key)
                            {
                                case SyntaxKind.AddExpression:
                                    op = "add";
                                    break;
                                case SyntaxKind.SubtractExpression:
                                    op = "substract";
                                    break;
                                case SyntaxKind.MultiplyExpression:
                                    op = "multiply";
                                    break;
                                case SyntaxKind.DivideExpression:
                                    op = "divide";
                                    break;
                                case SyntaxKind.ModuloExpression:
                                    op = "modulo";
                                    break;
                                default:
                                    throw new NotImplementedException();
                            }
                            return Syntax.Parameter(
                                typeOpt: Syntax.GenericName(
                                    Syntax.Identifier("System.Func"),
                                    Syntax.TypeArgumentList(
                                        arguments: SeparatedList.Create<TypeSyntax>(tc.Value.Select(t => t.Type)))),
                                identifier: Syntax.Identifier(string.Format("{0}{1}To{2}", op, tc.Value[0].GetText(), tc.Value[1].GetText())));
                        }));
                var operatorGenericConstraintAnnotation = new OperatorGenericConstraintAnnotation() { Operators = _typeConstraints.Keys.ToList() };
                _operatorGenericConstraintAnnotation.Add(operatorGenericConstraintAnnotation);
                value = (MethodDeclarationSyntax)value.WithAdditionalAnnotations(operatorGenericConstraintAnnotation);
                _typeConstraints = new Dictionary<SyntaxKind, List<TypeConstraintSyntax>>();
            }
            return value;
        }

        protected override SyntaxNode VisitBinaryExpression(BinaryExpressionSyntax node)
        {
            string op = null;
            switch (node.Kind)
            {
                case SyntaxKind.AddExpression:
                    op = "add";
                    break;
                case SyntaxKind.SubtractExpression:
                    op = "substract";
                    break;
                case SyntaxKind.MultiplyExpression:
                    op = "multiply";
                    break;
                case SyntaxKind.DivideExpression:
                    op = "divide";
                    break;
                case SyntaxKind.ModuloExpression:
                    op = "modulo";
                    break;
            }
            if (op != null)
            {
                var leftType = _semanticModel.GetSemanticInfo(node.Left).Type;
                var leftTypeAsTypeParameter = leftType as ITypeParameterSymbol;
                var rightType = _semanticModel.GetSemanticInfo(node.Right).Type;
                var rightTypeAsTypeParameter = rightType as ITypeParameterSymbol;
                if (leftTypeAsTypeParameter != null || rightTypeAsTypeParameter != null)
                    return Syntax.InvocationExpression(
                        Syntax.IdentifierName(string.Format("{0}{1}To{2}", op, leftType.Name, rightType.Name)),
                        Syntax.ArgumentList(
                            arguments: SeparatedList.Create<ArgumentSyntax>(
                                Syntax.Argument(
                                    expression: node.Left),
                                Syntax.Argument(
                                    expression: node.Right))));
            }
            return base.VisitBinaryExpression(node);
        }

        protected override SyntaxNode VisitTypeParameterConstraintClause(TypeParameterConstraintClauseSyntax node)
        {
            SyntaxKind op = 0;
            List<TypeConstraintSyntax> typeConstraintSyntaxes = new List<TypeConstraintSyntax>();
            IEnumerator<TypeConstraintSyntax> childNodes = node.ChildNodes().OfType<TypeConstraintSyntax>().GetEnumerator();
            bool equals = false;
            bool found = false;
            foreach (var n in node.ChildNodesAndTokens())
            {
                switch (n.Kind)
                {
                    case SyntaxKind.TypeConstraint:
                        childNodes.MoveNext();
                        var typeConstraintSyntax = childNodes.Current;
                        if (typeConstraintSyntax.GetText() == "where")
                            break;
                        if (typeConstraintSyntax.GetText().Length == 0)
                            typeConstraintSyntaxes.Add(Syntax.TypeConstraint(Syntax.IdentifierName(node.Identifier.GetText())));
                        else
                            typeConstraintSyntaxes.Add(typeConstraintSyntax);
                        if (equals)
                        {
                            _typeConstraints.Add(op, typeConstraintSyntaxes);
                            typeConstraintSyntaxes = new List<TypeConstraintSyntax>();
                            op = 0;
                            equals = false;
                            found = true;
                        }
                        break;
                    case SyntaxKind.PlusToken:
                        op = SyntaxKind.AddExpression;
                        break;
                    case SyntaxKind.MinusToken:
                        op = SyntaxKind.SubtractExpression;
                        break;
                    case SyntaxKind.AsteriskToken:
                        op = SyntaxKind.MultiplyExpression;
                        break;
                    case SyntaxKind.SlashToken:
                        op = SyntaxKind.DivideExpression;
                        break;
                    case SyntaxKind.PercentToken:
                        op = SyntaxKind.ModuloExpression;
                        break;
                    case SyntaxKind.EqualsToken:
                        equals = true;
                        break;
                }
            }
            if (found)
                return null;
            return base.VisitTypeParameterConstraintClause(node);
        }
    }
}
